<?php

/*******************************************************************************
 *  
 * CLASS [ db ]
 *
 *******************************************************************************
 * 
 * Classe generique d'abstraction sur la base de donnees
 * 
 *******************************************************************************
 * @package objects
 * @version 20070807
 * @author Stanislas PERRIN <stanislas.perrin@gmail.com> <stan@extramuros.com>
 * 
 * @licence GNU/LGPL v3
 * 
 * 
 * Attention !
 * Cette classe a besoin de la classe debug.class.php qui se trouve ici :
 * http://svn.extramuros.com/libs/debug.class.php
 *
 */


abstract CLASS db
{
	#########################################################################
	#
	# PROPRIETES
	#
	#########################################################################
	
	// Identifiant de la connexion a la BDD
	protected $dbConnectionId = 0;

	// Charset des donnees de la base
	protected $dbCharset = 'UTF-8';
		
	// Contenu de la requete
	protected $queryString = '';
	
	// Code de retour d'une requete (Ressource Code)
	public $queryResultRC = null;
	
	// Contenu du resultat de la requete
	protected $queryResultArray = array();

	// Nombre de lignes retournees ou affectees par la derniere requete
	protected $lines = 0;
	
	// Dernier code d'erreur MySQL
	protected $lastErrCode = 0;
	
	// Dernier message d'erreur MySQL
	protected $lastErrMessage = '';
	
	// Duree d'execution d'une requete
	protected $queryExecTime = 0;
	
	// Timestamp debut d'execution de la requete
	protected $queryTimerStart = 0;
	
	// Compteur d'instances
	protected static $instancesCount = 0;

	// Compteur "statique" de requetes
	protected static $totalQueryCount = 0;
	
	// Compteur "dynamique" de requetes (incrementation quand debut requete / decrementation quand fin requete)
	protected static $instantQueryCount = 0;
	
	// Duree totale des requetes
	protected static $totalQueryTime = 0;
	
	// Duree moyenne requete
	protected static $averageQueryTime = 0;
	
	# -----------------------------------------------------------------------
	# Tout pour la pile de requetes
	# -----------------------------------------------------------------------
	protected $queryStack = array();
	protected $queryStackExecutionDelay = 5; // millisecondes
	
	#########################################################################
	#
	# CONSTRUCTEUR
	#
	#########################################################################
	
	public function __construct($dbConnectionObject)
	{
		if (!$dbConnectionObject) throw new debug ("pas d'objet de connexion !",-100);
		$this->dbConnectionId = $dbConnectionObject->getId();
		// imposer le charset client
		mysql_set_charset($this->dbCharset,$this->dbConnectionId);
		// incrementation du compteur d'instances
		self::$instancesCount++;
	}

	#########################################################################
	#
	# DESTRUCTEUR
	#
	#########################################################################
	
	public function __destruct()
	{
			self::$instancesCount--;
	}
	
	#########################################################################
	#
	# METHODES PUBLIQUES
	#
	#########################################################################
	
	/**
	* executeQuery()
	*
	* execute une requete sur la bdd
	*
	* @param string $q requete a executer
	* @return boolean VRAI si c'est ok, FAUX sinon
	* 
	*/
	
	public function query($q=false)
	{
		// declarations
		$this->queryResultRC = false;
		
		// $q permet d'utiliser query() sans utiliser $this->queryString qui est protected
		// (utilisation autonome du requeteur)
		if (!$q && $this->queryString === '') throw new debug(get_class($this) . " la requete ($q) est vide !?;",-100);
		
		// Affectation de la requete
		// $this->queryString = $this->escapeQuery($q);
		$this->queryString = $q;
		
		
		# ---------------------------------------------------------------
		# Requête
		# ---------------------------------------------------------------
		// Compteurs de requetes (instantane + total)
		self::$instantQueryCount++;
		self::$totalQueryCount++;

		// Declenchement du "chrono"
		$this->queryTimerStart = $this->getMicroTime();
		
		// Requête proprement-dite
		$this->queryResultRC = @mysql_query($this->queryString,$this->dbConnectionId);
		
		// Recuperation des messages de MySQL
		$this->lastErrCode = mysql_errno($this->dbConnectionId);
		$this->lastErrMessage = mysql_error($this->dbConnectionId);
		
		// Test sur le resultat
		if (!$this->queryResultRC) throw new debug(get_class($this) ." : erreur sur la requete ! ({$this->queryString}) [{$this->lastErrMessage}({$this->lastErrCode})]",-100);

		// Arret du chrono + calcul du temps d'execution de la requete
		$this->queryExecTime = ($this->getMicroTime() - $this->queryTimerStart);
		
		// Requete terminee : decrementation du compteur d'instantances de requetes 
		self::$instantQueryCount--;
		
		// Duree totale de requete
		self::$totalQueryTime += $this->queryExecTime;
		self::$averageQueryTime = (self::$totalQueryTime / self::$totalQueryCount);
		
		// Calcul du nombre de lignes affectees / retournees
		if (is_bool($this->queryResultRC)) $this->lines = @mysql_affected_rows($this->dbConnectionId);
		else $this->lines = @mysql_num_rows($this->queryResultRC);

		# ---------------------------------------------------------------
		# Retour
		# ---------------------------------------------------------------
		// retourne la reponse de MySQL a la requete
		return $this->queryResultRC;		
	}
	
	/**
	* getExecTime()
	*
	* renvoie la duree d'execution (en millisecondes) de la derniere requete SQL
	*
	* @param
	*/
	public function getExecTime()
	{
		return $this->queryExecTime;
	}
	
	/**
	 * getLines()
	 * 
	 * retourne la valeur de << $this->line >> qui contient le nombre de ligne affectees 
	 * ou renvoyes par la derniere requete
	 * 
	 */
	 public function getLines()
	 {
	 	return $this->lines;
	 }
	 
	/**
	 * getField()
	 * 
	 * retourne *1* champ d'une requete
	 */
	 public function getField($query=false)
	 {
 	
	 	// Si $query renseigne : on effectue la requete avant d'en retourner le resultat
	 	if ($query) $this->query($query);
	 	// Test du resultat
 		if (!$this->queryResultRC) throw new debug(get_class($this) . ' : pas de resultat de requete - impossible de retourner un champ !', -999);
 		$tableau = mysql_fetch_row($this->queryResultRC);
 		if (!$tableau) throw new debug(get_class($this) . ' : tableau de resultat vide - rien a retourner !', -999);
 		return $this->convertStringCharset($tableau[0]);
	 }

	/************************************************************************
	 * 
	 * getTuples()
	 * 
	 * retourne un tableau de tuples
	 * @param string $query requête de selection des tuples
	 * 
	 * 
	 ************************************************************************/
	 // public function getTuples($query=false,$conditions=false)
	 public function getTuples($query=false)
	 {
	 	// Si $query renseigne : on effectue la requete avant d'en retourner le resultat
	 	if ($query) $this->query($query);

 		if (!$this->queryResultRC) throw new debug(get_class($this) . " : pas de resultat pour la requete << {$this->queryString} >> - impossible de retourner un champ !", -999);
 		
 		$tableauTuples = array();
 		
 		// Peupler le tableau de tuples
 		while ($tuple = mysql_fetch_assoc($this->queryResultRC))
 		{
 			if (!$tuple) throw new debug(get_class($this) . ' : tuple vide ou invalide ?!', -999);
 			array_push($tableauTuples,$tuple);
 		}
 		
 		// Retourner le tout
 		return $tableauTuples;
	 }
	 
	/***************************************************************************
	 * 
	 * escapeQuery()
	 * 
	 * Échappement des caractères spéciaux de la requête.
	 * 
	 **************************************************************************/
	 
	 private function escapeQuery($query=false)
	 {
		 if (!$query || $query === '') throw new debug(get_class($this) . " : ne peut échapper une requête vide !", -999);
		 
		 if (get_magic_quotes_gpc()) $query = stripslashes($query);
		 return mysql_real_escape_string($query, $this->dbConnectionId);
		 
	 }

	/************************************************************************
	 * 
	 * setCharset()
	 * 
	 * change le charset utilise pour lire / ecrire dans la base
	 * 
	 ************************************************************************/
	 public function setCharset($charset=false)
	 {
 		if(!$charset) throw new debug(get_class($this) . " : charset ($charset) vide ou invalide !", -999);
 		$this->dbCharset = $charset;
	 }


	#########################################################################
	#
	# GESTION DE LA PILE DE REQUETES
	#
	#########################################################################
	
	/**
	 * addToQueryStack()
	 */
	 public function addToQueryStack($queryString=false)
	 {
		if (!$queryString) throw new debug(get_class($this) . " : ne peut ajouter une requete SQL vide ($queryString) a la pile !", -999);
		$this->queryStack[count($this->queryStack)] = $queryString;
	 }

	/**
	 * removeFromQueryStack()
	 * 
	 * enleve (depile) un element de la pile
	 */
	 public function removeFromQueryStack($queryNumber=false)
	 {
		if (!$queryNumber) throw new debug(get_class($this) . " : ne peut enlever de la pile une requete (# $queryNumber) SQL dont je ne connais pas le numero !", -999);
		unset($this->queryStack[$queryNumber]);
	 }

	/**
	 * 
	 * emptyQueryStack()
	 * 
	 * vide la pile de requetes
	 * 
	 */
	 public function emptyQueryStack()
	 {
		if (!$this->queryStack[] = array()) throw new debug(get_class($this) . " : ne peut vider la pile de requetes",-999);
	 }

	/**
	 * clearQueryStack()
	 * 
	 * alias de $this->clearQueryStack()
	 * 
	 * vide la pile de requete
	 */
	 public function clearQueryStack()
	 {
	 	return $this->clearQueryStack();
	 }

	/**
	 * setQueryStackDelay()
	 * 
	 * regle l'intervalle entre l'execution de deux requetes de la pile 
	 */
	 public function setQueryStackExecutionDelay($delay=false)
	 {
		if (!$delay) throw new debug(get_class($this) . " : le delai ($delay) est vide ou invalide",-999);
		$this->queryStackExecutionDelay = $delay;
	 }

	/**
	 * getQueryStackDelay()
	 * 
	 * retourne l'intervalle entre l'execution de deux requetes de la pile
	 * 
	 */
	 public function getQueryStackExecutionDelay()
	 {
		// if (!$delay) throw new debug(get_class($this) . " : le delai ($delay) est vide ou invalide",-999);
		return $this->queryStackExecutionDelay;
	 }

	/**
	 * 
	 * executeQueryStack()
	 * 
	 */
	 public function executeQueryStack($delay=false)
	 {
	 	if (!$delay) $delay = $this->queryStackExecutionDelay;
	 	
	 	foreach ($this->queryStack as $index => $query)
	 	{
	 		$this->query($query);
	 		$this->wait($delay);
	 	}
	 	
	 	return true;
	 }

	/**
	 * 
	 * reverseExecuteQueryStack()
	 * 
	 */
	 public function reverseExecuteQueryStack($delay=false)
	 {
	 	if (!$delay) $delay = $this->queryStackExecutionDelay;
	 	
	 	// renversement de la pile
	 	$this->queryStack = array_reverse($this->queryStack);
	 	
	 	// execution de la pile
	 	$vrai = $this->executeQueryStack($delay);
	 	
	 	// on remet la pile a l'endroit
	 	$this->queryStack = array_reverse($this->queryStack);
	 	
	 	// et voilà !
	 	return true;
	 }

	/**
	 * 
	 * executeStackedQuery()
	 * 
	 */
	 public function executeStackedQuery($queryNumber=false)
	 {
		if (!$queryNumber) throw new debug(get_class($this) . " : je ne sais pas quel numero de requete ($queryNumber) executer !",-999);
		if (!isset($this->queryStack[$queryNumber]) || empty($this->queryStack[$queryNumber])) throw new debug(get_class($this) . " : la requete num # $queryNumber est vide ! ou n'existe pas",-999);
		return $this->query($this->queryStack[$queryNumber]);
	 }
	 

	#########################################################################
	#
	# GETTERS & SETTERS
	#
	#########################################################################
	
	/**
	 * get()
	 * 
	 * getter universel pour tous les membres de l'objet
	 */
	 protected function get($propertyName)
	 {
	 	return $this->{$propertyName};
	 }
	
	/**
	 * getProperty()
	 * 
	 * getter pour les propriete de l'objet metier manipule 
	 */
	 public function getProperty($propertyName=false)
	 {
		if (!$propertyName) throw new debug(get_class($this) . " : nom de propriete ($propertyName) vide ou invalide !", -999);
		return $this->{$propertyName};
	 }
	 
	/**
	 * set()
	 * 
	 * setter universel pour tous les membres de l'objet
	 */
	protected function set($propertyName=false,$propertyValue=null)
	{
		if (!$propertyName || !$propertyValue) throw new debug(get_class($this) . " : nom de propriete ($propertyName) ou valeur de propriete ($propertyValue) vide ou invalide !", -999);
		return $this->{$propertyName} = $propertyValue;
	}
  
	/**
	* setProperty()
	* 
	* alias de de set()
	* 
	*/
	public function setProperty($propertyName=false,$propertyValue=null)
	{
		return $this->set($propertyName,$propertyValue);
	}
	
	/**
	 * getQueryString()
	 * 
	 */
	public function getQueryString()
	{
		return $this->queryString;
	} 
  
	/**
	* getInstancesCount()
	*/
	public function getInstancesCount()
	{
		return self::$instancesCount;
	}
   
	/**
	* getTotalQueryCount()
	*/
	public function getTotalQueryCount()
	{
		return self::$totalQueryCount;
	}
	   

	/**
	* getInstantQueryCount()
	*/
	public function getInstantQueryCount()
	{
		return self::$instantQueryCount;
	}

	/**
	* getTotalQueryTime()
	*/
	public function getTotalQueryTime()
	{
		return self::$totalQueryTime;
	}
	
	/**
	* getAverageQueryTime()
	*/
	public function getAverageQueryTime()
	{
		return self::$averageQueryTime;
	}


	#########################################################################
	#
	# METHODES PROTEGEES
	#
	#########################################################################

	/**
	* convertStringCharset()
	*
	*/
	protected function convertStringCharset($string=false)
	{
 		// if (!$string) throw new debug(get_class($this) . " : chaine ($string) vide ou invalide", -999);
 	
		// Tableau de détection des encodages
		$arCharsetTypes = array('UTF-8','UTF-16','UTF-32',
								'ISO-8859-15','ISO-8859-1',
								'CP1251','CP1252',
								'ASCII');
		
 		// On trouve le charset de chaine de caracteres en entree
 		$inputStringCharset = mb_detect_encoding($string,$arCharsetTypes,TRUE);
 		// $inputStringCharset = ($inputStringCharset == '') ? 'UTF-8' : $inputStringCharset;
 		
 		print("DEBUG: $string\t$inputStringCharset\n");
 		 
 		// Comparaison avec le charset local et conversion si besoin
 		if ($inputStringCharset != $this->dbCharset)
 		{
 			// $string = iconv($inputStringCharset,$this->dbCharset,$string);
 			$string = mb_convert_encoding($string,$this->dbCharset,$inputStringCharset);
 		}

 		print("DEBUG: $string\n");

 		
 		// Retourner la chaine dans le bon charset
 		return $string;
	}

	
	#########################################################################
	#
	# METHODES PRIVEES
	#
	#########################################################################
	
	/**
	* getMicroTime()
	*
	* @access private
	* @param
	* @return float milliseconds
	*
	*/
	protected function getMicroTime()
	{
		/*
		list($usec, $sec) = explode(" ", microtime());
		return ((float)$usec + (float)$sec);
		*/
		return microtime(true) * 1000;
	}
		
	/**
	 * wait()
	 * 
	 * attend /n/ millisecondes
	 * 
	 * @param float $intervall temps d'attente en millisecondes
	 * 
	 */
	 protected function wait($intervall=0)
	 {
	 	$intervall = $intervall * 1000;
	 	@usleep($intervall);
	 	return true;
	 }
}

?>
